import wsgiref.handlers
import os
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template
import urllib
import re
import random
import datetime
from google.appengine.api import urlfetch
from urlparse import urlparse
import uzdb

class BaseRequestHandler(webapp.RequestHandler):
	def handle_exception(self, exception, debug_mode):
		exception_name = sys.exc_info()[0].__name__
		exception_details = str(sys.exc_info()[1])
		exception_traceback = ''.join(traceback.format_exception(*sys.exc_info()))
		logging.error(exception_traceback)
		exception_expiration = 3600 # seconds (max 1 mail per hour for a particular exception)
		mail_admin = "jackson.h.miller@gmail.com" # must be admin for the application
		sitename = "URLZEN"
		throttle_name = 'exception-'+exception_name
		throttle = memcache.get(throttle_name)
		if throttle is None:
			memcache.add(throttle_name, 1, exception_expiration)
			subject = '[%s] exception [%s: %s]' % (sitename, exception_name, exception_details)
			mail.send_mail_to_admins(sender=mail_admin,subject=subject,body=exception_traceback)
		self.response.set_status(200)
		template_values = {}
		path = os.path.join(os.path.dirname(__file__), 'templates/error.html')
		self.response.out.write(template.render(path, template_values))
	


class MainPage(BaseRequestHandler):
	def get(self):
		#recent_links = db.GqlQuery("SELECT * FROM Url ORDER BY created DESC LIMIT 5")
		recent_links = uzdb.Url().all().order("-created").fetch(5)
		popular_links = db.GqlQuery("SELECT * FROM Url ORDER BY counter DESC LIMIT 5")
		template_values = {
					'version': "app_version",
					'recent_links': recent_links,
					'domain': self.request.host,
					'ad_code': '',
					'popular_links': []
		}
		
		path = os.path.join(os.path.dirname(__file__), 'index.html')
		self.response.out.write(template.render(path, template_values))

class ApiPage(webapp.RequestHandler):
	def get(self):
		longurl = self.request.get('url')
		
		seq = db.GqlQuery("SELECT * FROM Sequence").get()
		if not seq:
			seq = uzdb.Sequence()
			seq.url_id = 0
			seq.put()
			seq = db.GqlQuery("SELECT * FROM Sequence").get()
			
		
		url = db.GqlQuery("SELECT * FROM Url WHERE url = :1",longurl).get()

		if not url:
			# create the URL and slug
			url = uzdb.Url()
			url.url = longurl
			url.url_id = db.run_in_transaction(get_url_id, seq.key())
			url.slug = base10toN(url.url_id,36)
			url.first_ip_addr = self.request.remote_addr
			url.put()
		
		self.response.out.write("http://"+self.request.host+"/"+url.slug)
		

class UrlPage(BaseRequestHandler):
	def get(self):
		longurl = self.request.get('url')
		autocopy = self.request.get('autocopy')
		if not autocopy:
			autocopy = 'false'
		if not longurl:
			longurl = urllib.unquote(self.request.path_qs.lstrip('/'))
		
		seq = db.GqlQuery("SELECT * FROM Sequence").get()
		if not seq:
			seq = uzdb.Sequence()
			seq.url_id = 0
			seq.put()
			seq = db.GqlQuery("SELECT * FROM Sequence").get()
			
		url = db.GqlQuery("SELECT * FROM Url WHERE url = :1",longurl).get()
		if not url:
			# create the URL and slug
			url = uzdb.Url()
			url.url = longurl
			url.url_id = db.run_in_transaction(get_url_id, seq.key())
			url.slug = base10toN(url.url_id,36)
			url.first_ip_addr = self.request.remote_addr
			# get the title (if the title wasn't passed in)
			try:
				url.page_title = re.compile('<title>.*</title>').search(urlfetch.fetch(longurl).content.replace('\n','')).group(0).replace('<title>','').replace('</title>','')
				url.put()
			except:
				url.page_title = longurl
				url.put()
		
		if url.domain_counter > 0:
			domain_data = db.GqlQuery("SELECT * FROM ReferrerDomain WHERE url_id = :1 ORDER BY counter DESC",url.url_id)
		else:
			domain_data = {}
		
		if autocopy == 'true':
			onload = "copy('http://"+self.request.host+"/"+url.slug+"');"
		else:
			onload = "document.getElementById('copybtn').focus();"
		banner = get_ad()
			
		template_values = {
			'version': "app_version",
			'url': url.url,
			'slug': url.slug,
			'clicks': url.counter,
			'domains': url.domain_counter,
			'domain': self.request.host,
			'domain_data': domain_data,
			'ad_code': banner.ad_code,
			'onload': onload
		}
		path = os.path.join(os.path.dirname(__file__), 'url.html')
		self.response.out.write(template.render(path, template_values))
	


# The twitter page should query the data store for a StringListProperty of all twitter users associated with a URL.
# the post method should assign a twitter user to a URL.
class TwitterPage(BaseRequestHandler):
	def get(self):
		user = urllib.unquote(self.request.path.replace('/twitter/',''))
		urls = db.GqlQuery("SELECT * FROM Url WHERE twitter_users = :1 ORDER BY created DESC",user)
		template_values = {
			'version': "app_version",
			'urls': urls,
			'domain': self.request.host,
			'twitter_user': user
		}
		#self.response.out.write("Hrmmmmm.")
		path = os.path.join(os.path.dirname(__file__), 'twitter.html')
		self.response.out.write(template.render(path, template_values))
	
	def post(self):
		user = self.request.get('twitter_user')
		twitter_user = db.GqlQuery("SELECT * FROM TwitterUser WHERE twitter_user= :1",user).get()
		if not twitter_user:
			twitter_user = TwitterUser()
			twitter_user.twitter_user = user
		slug = self.request.get('urlzen_id')
		url = db.GqlQuery("SELECT * FROM Url WHERE slug = :1",slug).get()
		if not url.twitter_users:
			url.twitter_users = []
		if not user in url.twitter_users:
			url.twitter_users.append(user)
			url.put()
			twitter_user.url_counter = twitter_user.url_counter + 1
			twitter_user.put()
		tweet_id = self.request.get('tweet_id')
		if tweet_id:
			tweet = db.GqlQuery("SELECT * FROM Tweet WHERE slug = :1 AND tweet_id = :2",slug,tweet_id).get()
			if not tweet:
				tweet = uzdb.Tweet()
				tweet.slug = slug
				tweet.tweet_id = tweet_id
				tweet.twitter_user = user
				tweet.tweet = self.request.get('tweet')
				tweet.put()

class LeaderboardPage(BaseRequestHandler):
	def get(self):
		# use map reduce to give the following info:
		# top urls
		# top domains (linked to)
		# top domains (clicked from)
		# top twitter users (clicks)
		# top twitter users (domains)
		one_day_ago = db.DateTimeProperty().now() - datetime.timedelta(minutes = 1440)
		daily_urls = db.GqlQuery("SELECT * FROM Url ORDER BY counter DESC LIMIT 25")
		one_week_ago = db.DateTimeProperty().now() - datetime.timedelta(minutes = 10080)
		weekly_urls = db.GqlQuery("SELECT * FROM Url ORDER BY counter DESC LIMIT 25")
		template_values = {
			'version': "app_version",
			'daily_urls': daily_urls,
			'weekly_urls': weekly_urls,
			'domain': self.request.host
		}
		#self.response.out.write("Hrmmmmm.")
		path = os.path.join(os.path.dirname(__file__), 'leaderboard.html')
		self.response.out.write(template.render(path, template_values))
		
class RedirectPage(BaseRequestHandler):
	def head(self):
		slug = urllib.unquote(self.request.path.lstrip('/'))
		
		url = db.GqlQuery("SELECT * FROM Url WHERE slug = :1",slug).get()
		if not url:
			self.response.out.write('unknown url')
			return
		self.redirect(url.url,True)
	
		
	def get(self):
		slug = urllib.unquote(self.request.path.lstrip('/'))
		
		url = db.GqlQuery("SELECT * FROM Url WHERE slug = :1",slug).get()
		if not url:
			self.redirect(self.request.host_url)
			return

		db.run_in_transaction(increment_counter, url.key(), 1)
		
		
		try:
			referrer = os.environ.get("HTTP_REFERER","none")
			if referrer == "none":
				ref_domain = "none"
			else:
				ref_domain = urlparse(referrer).hostname
				#ref_domain = "none"
		except KeyError, k:
			referrer = "none"
			ref_domain = "none"

		
		domain = db.GqlQuery("SELECT * FROM ReferrerDomain WHERE url_id = :1 AND domain = :2",url.url_id,ref_domain).get()
		if not domain:
			domain = uzdb.ReferrerDomain()
			domain.url_id = url.url_id
			domain.domain = ref_domain
			domain.put()
			db.run_in_transaction(increment_domain,url.key(),1)
			domain = db.GqlQuery("SELECT * FROM ReferrerDomain WHERE url_id = :1 AND domain = :2",url.url_id,ref_domain).get()
		
		db.run_in_transaction(increment_counter, domain.key(),1)
		
		ref = db.GqlQuery("SELECT * FROM Referrer WHERE url_id = :1 AND referrer = :2",url.url_id,referrer).get()
		if not ref:
			ref = uzdb.Referrer()
			ref.domain_id = domain.key().id()
			ref.referrer = referrer
			ref.url_id = url.url_id
			ref.put()
			ref = db.GqlQuery("SELECT * FROM Referrer WHERE url_id = :1 AND referrer = :2",url.url_id,referrer).get()

		db.run_in_transaction(increment_counter, ref.key(),1)
		
		click = uzdb.Click()
		click.url_id = url.url_id
		click.referrer_id = ref.key().id()
		click.put()
		
		self.redirect(url.url)
		#self.response.out.write('<html><head><meta http-equiv="refresh"" content="0; url=%s" /></head><body></body></html>' % url.url)
	

class ErrorPage(webapp.RequestHandler):
	def get(self):
		self.response.set_status(200)
		template_values = {}
		path = os.path.join(os.path.dirname(__file__), 'templates/error.html')
		self.response.out.write(template.render(path, template_values))
	

#class RssPage(webapp.RequestHandler):
	
def get_ad(): 
	#return db.GqlQuery("SELECT * FROM Banner WHERE rand > :1 ORDER BY rand LIMIT 1",random.random()).get()
	return uzdb.Banner()

def main():
	application = webapp.WSGIApplication(
																			 [('/', MainPage),
																				('/leaderboard.*',LeaderboardPage),
																				('/twitter.*',TwitterPage),
																				('/https.*', UrlPage),
																				('/http.*', UrlPage),
																				('/shorten/.*', UrlPage),
																				('/api.*', ApiPage),
																				('/error.*',ErrorPage),
																				('/.*', RedirectPage)],
																			 debug=True)
	wsgiref.handlers.CGIHandler().run(application)

# increment counters in a transaction
def increment_counter(key, amount):
	obj = db.get(key)
	obj.counter += amount
	obj.put()
	
def get_url_id(key):
	s = db.get(key)
	if not s:
		s = uzdb.Sequence()
		s.url_id = 0
		#s.put()
	s.url_id = s.url_id + 1
	s.put()
	return s.url_id
	
def increment_domain(key,amount):
	obj = db.get(key)
	obj.domain_counter += amount
	obj.put()

# used to convery the key to base 36
def base10toN(num,n):
		"""Change a	 to a base-n number.
		Up to base-36 is supported without special notation."""
		num_rep={10:'a',11:'b',12:'c',13:'d',14:'e',15:'f',16:'g',17:'h',18:'i',19:'j',20:'k',21:'l',22:'m',23:'n',24:'o',25:'p',26:'q',27:'r',28:'s',29:'t',30:'u',31:'v',32:'w',33:'x',34:'y',35:'z'}
		new_num_string=''
		current=num
		while current!=0:
				remainder=current%n
				if 36>remainder>9:
						remainder_string=num_rep[remainder]
				elif remainder>=36:
						remainder_string='('+str(remainder)+')'
				else:
						remainder_string=str(remainder)
				new_num_string=remainder_string+new_num_string
				current=current/n
		return new_num_string

if __name__ == "__main__":
	main()